﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AutoAllocService.Map;
using AutoAllocService.Map.Models;
using AutoAllocService.Messaging;
using AutoAllocService.Models;
using AutoAllocService.Services.Enums;
using AutoAllocService.Services.Models;
using AutoAllocService.Task;
using log4net;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Polly;
using Polly.Retry;
using RestEase;

namespace AutoAllocService.Services
{
    public class AllocTask : IDisposable
    {
        private readonly ITimeJobApi _jobApi;
        private readonly ICallCenterApi _callCenterApi;
        private readonly ILog _log = LogManager.GetLogger(typeof(AllocTask));

        public TaskStatus Status { get; private set; }

        public string BookingID { get; }

        private readonly BookingModel _booking;
        private readonly AllocTaskStore _store;
        private readonly RetryPolicy _policy;

        public AllocTask(BookingModel booking)
        {
            _policy = Policy.Handle<Exception>()
                .RetryAsync(3, (excetion, retryCount) =>
                {
                    _log.Error(excetion);
                });

            BookingID = booking.BookingID;
            _booking = booking;
            _store = AllocTaskStore.Init(BookingID);

            _jobApi = RestClient.For<ITimeJobApi>(Program.AppSetting.TimeJobApi);
            var defaultJsonSerializerSettings = new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore,
                Formatting = Formatting.None
            };
            defaultJsonSerializerSettings.Converters.Add(new JsonDateTimeConverter());
            defaultJsonSerializerSettings.NullValueHandling = NullValueHandling.Include;

            _callCenterApi = new RestClient(Program.AppSetting.CallCenter.Url)
            {
                JsonSerializerSettings = defaultJsonSerializerSettings
            }.For<ICallCenterApi>();
        }

        public async System.Threading.Tasks.Task Start()
        {
            if (_store.AllocTimes > Program.AppSetting.DriverAllocingExpires)
                return;
            if (DateTime.Now - _store.LatestRunAt < TimeSpan.FromMinutes(2))
            {
                //var next = 2 - (DateTime.Now.Minute - _store.LatestRunAt.Minute);
                //if (next < 1) next = 1;
                //await SetNextReCall(next);
                return;
            }
            Status = TaskStatus.Running;

            var driverCode = await GetDriverCode();
            if (string.IsNullOrEmpty(driverCode))
            {
                SendNoDriver();
            }
            else
            {
                _store.AllocedDrivers.Add(driverCode);

                var result = await _callCenterApi.GetBooking(BookingID);
                var onlineBooking = result.Data;

                if (onlineBooking.Status != "Registered")
                {
                    _log.Warn($"Booking {onlineBooking.BookingID} status is {onlineBooking.Status} , Task Stopped");
                    return;
                }

                OrderModel order = null;

                if (string.IsNullOrEmpty(onlineBooking.POID))
                {
                    var orderResult = await _callCenterApi.CreatePickupOrder(new UpdateOrderModel
                    {
                        BookingID = BookingID,
                        DriverCode = driverCode
                    });
                    order = orderResult.Data;
                }
                else if (await CheckOrderStatus(onlineBooking.POID))
                {
                    return;
                }
                else
                {
                    await SendChangeDriverMsg(onlineBooking.POID);
                    var orderResult = await _callCenterApi.UpdatePickupOrder(new UpdateOrderModel
                    {
                        BookingID = BookingID,
                        DriverCode = driverCode
                    });
                    order = orderResult.Data;
                }
                if (order != null)
                {
                    _store.LatestDriver = driverCode;
                    SendPending(order);
                    await _policy.ExecuteAsync(() => SendMqttAssign(order));
                }
            }

            _store.AllocTimes++;

            await SetNextReCall();
        }

        private async System.Threading.Tasks.Task SetNextReCall(int minutes = 2)
        {
            var payload = new MqTaskModel
            {
                Bookings = new List<BookingModel> { _booking },
                Type = "assign"
            };
            await _jobApi.Schedule(new AddMQScheduleModel
            {
                Delay = TimeSpan.FromMinutes(minutes),
                Topic = "auto_alloc.request",
                Key = BookingID,
                Payload = JsonConvert.SerializeObject(payload)
            });
        }

        public void Dispose()
        {
            _store.LatestRunAt = DateTime.Now;
            _store.Save();
        }

        private async System.Threading.Tasks.Task<string> GetDriverCode()
        {
            var drivers = await DriverService.GetDrivers((double)_booking.PickupLatitude, (double)_booking.PickupLongitude,
                _booking.BookingID);

            if (drivers == null || !drivers.Any())
            {
                return null;
            }

            foreach (var driver in drivers)
            {
                if (_store.AllocedDrivers.Contains(driver.Entity))
                {
                    continue;
                }
                AllocTaskStore.AddUsingDriver(driver.Entity);
                return driver.Entity;
            }

            return null;
        }

        private async System.Threading.Tasks.Task<bool> CheckOrderStatus(string poid)
        {
            var order = await _policy.ExecuteAsync(() => GetOrder(poid));

            if (order.OrderState != "PendConfirm")
            {
                SendSuccess(order);
                return true;
            }
            return false;
        }

        private async System.Threading.Tasks.Task SendChangeDriverMsg(string poid)
        {
            var order = await _policy.ExecuteAsync(() => GetOrder(poid));

            await _policy.ExecuteAsync(() => SendMqttChangeDriver(order));

            SendFaild(order);
        }

        private async System.Threading.Tasks.Task<OrderModel> GetOrder(string poid)
        {
            var order = await _callCenterApi.GetPickupOrder(poid);
            if (order.Status != 1)
            {
                throw new Exception(order.Message);
            }
            return order.Data;
        }

        private void SendNoDriver()
        {
            _store.LastStatus = "NO DRIVER";
            RabbitMqHelper.Instance.EnqueueResult(new AllocMqMsg
            {
                BookingID = BookingID,
                Type = "NODRIVER"
            });
        }

        private void SendSuccess(OrderModel order)
        {
            var payload = new
            {
                Type = "SUCCESS",
                BookingID,
                order.POID,
                DriverCode = _store.LatestDriver,
                order.DriverID,
                Cdt = DateTime.Now
            };
            RabbitMqHelper.Instance.EnqueueResult(payload);
            _log.Info($"Send Success to order {order.POID} with driver {_store.LatestDriver}");
        }

        private void SendPending(OrderModel order)
        {
            var payload = new
            {
                Type = "PENDING",
                BookingID,
                order.POID,
                DriverCode = _store.LatestDriver,
                order.DriverID,
                Cdt = DateTime.Now
            };
            RabbitMqHelper.Instance.EnqueueResult(payload);
            _log.Info($"Send Pending to order {order.POID} with driver {_store.LatestDriver}");
        }

        private void SendFaild(OrderModel order)
        {
            var payload = new
            {
                Type = "FAILED",
                BookingID,
                order.POID,
                DriverCode = _store.LatestDriver,
                order.DriverID,
                Cdt = DateTime.Now
            };
            RabbitMqHelper.Instance.EnqueueResult(payload);
            _log.Info($"Send Faild to order {order.POID} with driver {_store.LatestDriver}");
        }

        private async System.Threading.Tasks.Task SendMqttChangeDriver(OrderModel order)
        {
            if (string.IsNullOrEmpty(_store.LatestDriver))
                return;

            var pickup = _booking.PickupAddress;
            var dest = _booking.DestAddress;
            var date = _booking.PickupTime.ToString("MM月dd日HH:mm");

            var msg = $"由于您未确认接单，{date}由{pickup}前往{dest}的订单已派给其他司机。";

            var payload = JsonConvert.SerializeObject(new
            {
                Type = 409,
                Message = msg,
                Data = new
                {
                    order.POID
                }
            });
            var topic = $"/CD/TH/Driver/{_store.LatestDriver}/cmd";
            await MqttHelper.Instance.PublishAsync(topic, payload);
        }

        private async System.Threading.Tasks.Task SendMqttAssign(OrderModel order)
        {
            var pickup = _booking.PickupAddress;
            var dest = _booking.DestAddress;
            var date = _booking.PickupTime.ToString("MM月dd日HH:mm");

            var msg = $"您有新的订单，{date}由{pickup}前往{dest}，请尽快在系统确认接单。";

            var payload = JsonConvert.SerializeObject(new
            {
                Type = 409,
                Message = msg,
                Data = new
                {
                    order.POID
                }
            });
            var topic = $"/CD/TH/Driver/{_store.LatestDriver}/cmd";
            await MqttHelper.Instance.PublishAsync(topic, payload);
        }
    }
}
